/**
 * File containing the definition of the MirrorChart class
 * @author  Lorenzo Baracchi <lorenzo.baracchi@usi.ch> 
 */

/**
 * The MirrorChart class It creates a bar chart above and a mirror below the x
 * axis Option to do also something in the middle
 */
function MirrorChart(canvas, labels, dataUp, dataDown, stroke) {
	this.canvas = canvas;
	this.context = this.canvas.getContext("2d");
	this.labels = labels; // array of the labels
	this.dataUp = dataUp; // data to display above
	this.dataDown = dataDown; // data to display below
	this.dataMiddle = new Array();

	this.stroke = stroke; // the standard color for lines

	// define height of up,down and middle panels
	this.middleHeight = 65;
	this.upDownHeight = (this.canvas.height - this.middleHeight) / 2;
	this.startUp = 0;
	this.endUp = this.startUp + this.upDownHeight;
	this.startMiddle = this.endUp;
	this.endMiddle = this.startMiddle + this.middleHeight;
	this.startDown = this.endMiddle;
	this.endDown = this.startDown + this.upDownHeight;

	this.currentRange = [];
	this.padding = 5;

	// maximum values
	this.maxYUp = 0;
	this.maxYDown = 0;
	this.maxYMiddle = 0;

	// background
	this.hasBackground = false;
	this.backUp = null;
	this.backDown = null;
	
	this.addDataUP = function(data) {
		this.dataUp.push(data);
	}
	this.addDataDown = function(data) {
		this.dataDown.push(data)
	}
	this.addDataMiddle = function(data) {
		this.dataMiddle.push(data)
	}

	this.setMaxYUp = function() {
		for ( var i in this.dataUp.y) {
			if (this.dataUp.y[i] > this.maxYUp)
				this.maxYUp = this.dataUp.y[i];
		}
	}
	this.setMaxYDown = function() {
		for ( var i in this.dataDown.y) {
			if (this.dataDown.y[i] > this.maxYDown)
				this.maxYDown = this.dataDown.y[i];
		}
	}
	this.setMaxYMiddle = function() {
		for ( var i in this.dataMiddle.y) {
			if (this.dataMiddle.y[i] > this.maxYMiddle)
				this.maxYMiddle = this.dataMiddle.y[i]
		}
	}

	this.setBackground = function(backUp, backDown) {
		this.hasBackground = true;
		this.backUp = new Image();
		this.backUp.src = backUp;
		this.backDown = new Image();
		this.backDown.src = backDown;
	}

	/**
	 * Return the width of a single unit of x-axis
	 */
	this.widthX = function() {
		return this.canvas.width
				/ (this.currentRange[1] - this.currentRange[0]) - this.padding;
	}

	/**
	 * Return an x value that can be used for the chart
	 */
	this.getChartX = function(x) {
		return this.canvas.width
				/ (this.currentRange[1] - this.currentRange[0]) * x;
	}

	/**
	 * Return where the position of the orign for y
	 */
	this.getOriginY = function(where) {
		switch (where) {
		case "up":
			return this.startUp;
			break;
		case "down":
			return this.startDown;
			break;
		case "middle":
			return this.startMiddle;
			break;
		}
	}

	/**
	 * Return a y value that can be used for the chart where determines the
	 * position in one of the three planes (up, down, middle)
	 */
	this.getChartY = function(y, where) {
		switch (where) {
		case "up":
			return this.endUp - (this.endUp / this.maxYUp * y);
			break;
		case "down":
			return (this.endDown - this.startDown) / this.maxYDown * y;
			break;
		case "middle":
			return this.startMiddle;
			break;
		}
	}

	this.getCurrentRange = function() {
		return this.currentRange;
	}

	/**
	 * Draw the axis
	 */
	this.drawAxis = function(range) {
		// x axis
		for ( var a = range[0]; a < range[1]; a++) {
			var x = this.getChartX(a - range[0]);
			var y = this.startMiddle;
			var w = this.widthX();

			// label
			this.context.fillStyle = this.stroke;
			if (this.labels == null)
				this.context.fillText(a + "", x + w / 3,
						this.canvas.height - 10);
			else {
				this.context.save();
				this.context.translate(x + w / 3, y);
				this.context.rotate(Math.PI / 2);
				if (this.labels[a] != null)
					this.context.fillText(" " + this.labels[a], 0, 0);
				else
					this.context.fillText("", 0, 0 + 20);
				this.context.restore();
			}
		}

		// divisors for middle panel
		this.context.strokeStyle = this.stroke;
		this.context.beginPath();
		this.context.moveTo(0, this.startMiddle);
		this.context.lineTo(this.canvas.width, this.startMiddle);
		this.context.closePath();
		this.context.stroke();

		this.context.beginPath();
		this.context.moveTo(0, this.endMiddle);
		this.context.lineTo(this.canvas.width, this.endMiddle);
		this.context.closePath();
		this.context.stroke();

	}

	this.clear = function() {
		this.context.clearRect(0, 0, canvas.width, canvas.height);
	}

	this.redraw = function() {
		this.clear();
		this.drawChart(this.currentRange, true);
	}

	/**
	 * Draws the background
	 */
	this.drawBackground = function() {
		this.context.drawImage(this.backUp, 0, this.startUp);
		this.context.drawImage(this.backDown, 0, this.startDown);
	}

	/**
	 * Draws the chart for the given range, the parameter redraw is optional
	 */
	this.drawChart = function(range, redraw) {

		this.currentRange = range;
		redraw = typeof redraw !== 'undefined' ? redraw : false;
		this.drawParameters = range;
		var tooltip = null;
		
		this.context.font = "11px Ubuntu";

		if (this.hasBackground)
			this.drawBackground();
		this.drawAxis(range);

		// draw up panel
		switch (this.dataUp.type) {
		case "bar":
			var t = this.drawBar(this.dataUp, range, "up", redraw);
			if (t[2] != "")
				tooltip = t;
			break;
		case "point":
			break;
		case "line":
			break;
		}

		// draw down panel
		switch (this.dataDown.type) {
		case "bar":
			var t = this.drawBar(this.dataDown, range, "down", redraw);
			if (t[2] != "")
				tooltip = t;
			break;
		case "point":
			break;
		case "line":
			break;
		}

		// draw middle panel
		// TODO
		var t = this.drawColorScale(this.dataMiddle, range, "middle", redraw);
		if(t[2] != "")
			tooltip = t;

		// draw tooltip
		if (tooltip != null) {
			var x = tooltip[0], y=tooltip[1]-10;
			var radius = 2;
			var dimText = this.context.measureText(tooltip[2])
			var width = dimText.width+2;
			var height = 11+2;
			this.context.strokeStyle = this.stroke;
			this.context.fillStyle = "rgb(255,255,255)";
			this.context.strokeRect(x, y, width, height);
			this.context.fillRect(x, y, width, height);
			//text
			this.context.fillStyle = this.stroke;
			this.context.fillText(tooltip[2], tooltip[0], tooltip[1]);
		}
	}

	/**
	 * Draw the bar chart for the series, within the given range and the panel
	 * where to draw. The parameter redraw is optional
	 */
	this.drawBar = function(series, range, where, redraw) {

		redraw = typeof redraw !== 'undefined' ? redraw : false;
		if (!redraw) {
			for ( var i in this.dataUp)
				series.shapes = new Array(series.x.length)
			for ( var i in this.dataDown)
				series.shapes = new Array(series.x.length)
		}

		this.context.strokeStyle = this.stroke;
		this.context.fillStyle = series.getColor()+"";

		var tooltipX = -1;
		var tooltipY = -1;
		var tooltipText = "";
		for ( var j = 0; j < series.y.length; j++) {
			if (series.x[j] < range[0] || series.x[j] > range[1])
				continue; // the data is not in the rage to draw
			if (where == "up") {
				var xToDraw = this.getChartX(series.x[j] - range[0]);
				var yToDraw = this.getChartY(series.y[j], where);
				var widthToDraw = this.widthX();
				var heightToDraw = this.endUp / this.maxYUp * series.y[j];
			}
			if (where == "down") {
				var xToDraw = this.getChartX(series.x[j] - range[0]);
				var yToDraw = this.getOriginY(where)
				var widthToDraw = this.widthX();
				var heightToDraw = this.getChartY(series.y[j], where);
			}

			if (series.shapes[j] != null) {
				if (series.shapes[j].selected){
					this.context.fillStyle = "rgb(77,61,125)";
				}					
				else if (series.shapes[j].over) {
					tooltipText = "#" + series.y[j];
					tooltipX = xToDraw - tooltipText.length;
					tooltipY = this.getOriginY(where) + this.upDownHeight / 2;
					this.context.fillStyle = "rgb(124,98,201)";
				} else
					this.context.fillStyle = series.getColor()+"";
			} else
				this.context.fillStyle = series.getColor()+"";
			this.context.fillRect(xToDraw, yToDraw, widthToDraw, heightToDraw);

			if (series.shapes[j] == null || !redraw) {
				var shape = new Shape(xToDraw, yToDraw, widthToDraw,
						heightToDraw, series.getColor()+"");
				series.setShape(j, shape);
			}
		}
		
		return [ tooltipX, tooltipY, tooltipText ];
	}
	
	/**
	 * Draw the color scale for the series, within the given range and the panel
	 * where to draw. The parameter redraw is optional
	 */
	this.drawColorScale = function(series, range, where, redraw) {

		redraw = typeof redraw !== 'undefined' ? redraw : false;
		if (!redraw) {
			for ( var i in this.dataMiddle)
				series.shapes = new Array(series.x.length)
		}

		this.context.strokeStyle = this.stroke;
		this.context.fillStyle = series.getColor()+"";

		var tooltipX = -1;
		var tooltipY = -1;
		var tooltipText = "";
		
		//determine max and min values
		var minColor = new Color(0,0,255,series.color.alpha);
		var maxColor = new Color(255,0,0,series.color.alpha);
		var maxVal = series.y[0];
		var minVal = series.y[0];
		for ( var j = 1; j < series.y.length; j++) {
			if(series.y[j]>maxVal)
				maxVal = series.y[j];
			if(series.y[j]<minVal)
				minVal = series.y[j]
		}
		
		// draw color scale
		for ( var j = 0; j < series.y.length; j++) {
			var xToDraw = this.getChartX(series.x[j] - range[0]);
			var yToDraw = this.getChartY(series.y[j], where);
			var widthToDraw = this.widthX()+this.padding;
			var heightToDraw = this.endMiddle-this.startMiddle;
			var col = this.determineColor(series.y[j], minColor, maxColor, minVal, maxVal)
			this.context.fillStyle = col.toString()+"";
			if (series.shapes[j] != null) {
				if (series.shapes[j].over) {
					tooltipText = "#" + series.y[j];
					tooltipX = xToDraw - tooltipText.length;
					tooltipY = this.getOriginY(where) + (this.endMiddle-this.startMiddle) / 2;
				}				
			}
			this.context.fillRect(xToDraw, yToDraw, widthToDraw, heightToDraw);
			
			if (series.shapes[j] == null || !redraw) {
				var shape = new Shape(xToDraw, yToDraw, widthToDraw,
						heightToDraw, series.getColor()+"");
				series.setShape(j, shape);
			}
		}
		return [ tooltipX, tooltipY, tooltipText ];
	}
	
	/**
	 * Determine the color to draw given the value, the color's range, min and max values
	 */
	this.determineColor = function(y, minColor, maxColor, minVal, maxVal){
		var step = 255/(maxVal-minVal);
		var valInScale = y-minVal;
		var c = new Color(parseInt(valInScale*step), 0, parseInt(minColor.blue-valInScale*step), minColor.alpha)
		return c;
	}

	/* Event listeners for mouse movements */

	this.selectedShape = -1;
	this.selectedData = -1;
	this.overShape = -1;
	this.overData = -1;
	var myState = this; // need to have a reference to the chart in the
	// listeners

	/**
	 * Click of the mouse
	 */
	canvas
			.addEventListener(
					'mousedown',
					function(e) {
						var selected = false;
						var mouse = myState.getMouse(e);
						var mx = mouse.x;
						var my = mouse.y;
						var datas = [ myState.dataUp, myState.dataDown, myState.dataMiddle ];
						for ( var k in datas) {
							var shapes = datas[k].shapes;
							var l = shapes.length;
							for ( var i = 0; i < l; i++) {
								if (shapes[i] != null
										&& shapes[i].contains(mx, my)) {
									if (myState.selectedData != -1
											&& myState.selectedShape != -1
											&& datas[myState.selectedData].shapes[myState.selectedShape] != null)
										datas[myState.selectedData].shapes[myState.selectedShape].selected = false;
									myState.selectedShape = i;
									myState.selectedData = k;
									shapes[i].selected = true;
									selected = true;
									if(datas[k].selectedFunction!=null)
										datas[k].selectedFunction(datas[k].id, datas[k].information[i]);
									else
										alert("nullfunction")
									break;
								}
								if (selected)
									break;
							}
						}
						if (!selected) {
							if (myState.selectedShape != null
									&& shapes[myState.selectedShape] != null
									&& datas[myState.selectedData].shapes[myState.selectedShape] != null) {
								datas[myState.selectedData].shapes[myState.selectedShape].selected = false;
								// this.selectedShape = null;
							}
						}
						myState.redraw();

					}, true);

	/**
	 * Mouse movement
	 */
	canvas
			.addEventListener(
					'mousemove',
					function(e) {
						var over = false;
						var mouse = myState.getMouse(e);
						var mx = mouse.x;
						var my = mouse.y;
						var datas = [ myState.dataUp, myState.dataDown, myState.dataMiddle ];
						for ( var k in datas) {
							var shapes = datas[k].shapes;
							var l = shapes.length;
							for ( var i = 0; i < l; i++) {
								if (shapes[i] != null
										&& shapes[i].contains(mx, my)) {
									if (myState.overData != -1
											&& myState.overShape != -1
											&& datas[myState.overData].shapes[myState.overShape] != null)
										datas[myState.overData].shapes[myState.overShape].over = false;
									myState.overShape = i;
									myState.overData = k;
									shapes[i].over = true;
									over = true;
									break;
								}
							}
							if (over)
								break;
						}
						if (!over) {
							if (myState.hoverShape != -1
									&& shapes[myState.overShape] != null
									&& datas[myState.overData].shapes[myState.selectedShape] != null) {
								datas[myState.overData].shapes[myState.selectedShape].over = false;
								// this.overShape = null;
							}
						}

						myState.redraw();
					}, true);

	/**
	 * Get the position of the mouse
	 */
	this.getMouse = function(e) {
		var targ;
		if (!e)
			e = window.event;
		if (e.target)
			targ = e.target;
		else if (e.srcElement)
			targ = e.srcElement;
		if (targ.nodeType == 3) // defeat Safari bug
			targ = targ.parentNode;

		// jQuery normalizes the pageX and pageY
		// pageX,Y are the mouse positions relative to the document
		// offset() returns the position of the element relative to the document
		var mx = e.pageX - $(targ).offset().left;
		var my = e.pageY - $(targ).offset().top;

		// We return a simple javascript object (a hash) with x and y defined
		return {
			x : mx,
			y : my
		};
	}

}